library(Seurat)
library(SnapATAC)
library(tidyverse)
library(DescTools)  # 4 AUC function
library(glue)
library(ggalluvial)  # 4 river plot
library(ggpubr)
source("~/multiOmic_benchmark/utils.R")
source("~/multiOmic_benchmark/KNN_agreement.R")
gg_color_hue <- function(n) {
  hues = seq(15, 375, length = n + 1)
  hcl(h = hues, l = 65, c = 100)[1:n]
}
## Make output directory
outdir <- "~/multiOmic_benchmark/report/output/20191113_labelTransferEDA_F74_v2/"
ifelse(!dir.exists(outdir), dir.create(outdir), FALSE)
[1] FALSE
# model.cca <- readRDS("~/models/modelCCA_union_hvg_F74_SCElist_20191113.RDS")
# model.liger <- readRDS("~/models/modelLiger_union_hvg_F74_SCElist_20191113.RDS")
# model.conos <- readRDS("~/models/modelConos_union_hvg_F74_SCElist_20191113.RDS")
seu.cca <- readRDS("~/models/labelTransferCCA_union_hvg_F74_SCElist_20191119.RDS")
seu.liger <- readRDS("~/models/labelTransferLiger_union_hvg_F74_SCElist_20191119.RDS")
seu.conos <- readRDS("~/models/labelTransferConos_union_hvg_F74_SCElist_20191119.RDS")
integrate_features <- scan("~/intFeatures_union_hvg_2000_F74_SCElist_20191113.txt", what='')
Read 10603 items
int.list <- list(CCA=seu.cca, Liger=seu.liger, Conos=seu.conos)
# ## Make method color palette
# method.palette <- brewer_palette_4_values(names(int.list), "Set1")

Embeddings

Visualize label transfer on original ATAC data (embedded SnapATAC bins)

## Load original data
orig.ATAC <- readRDS("~/my_data/cellranger-atac110_count_30439_WSSS8038360_GRCh38-1_1_0.snapATAC.RDS")
sce.list <- readRDS("~/my_data/integrated_thymus/F74_SCElist_20191119.RDS")
orig.RNA <- sce.list$RNA
## Make SeuratObjects
atac.seu <- snapToSeurat(
    obj=orig.ATAC, 
    eigs.dims=1:20, 
    norm=TRUE,
    scale=TRUE
    )
Epoch: checking input parameters ... 
Non-unique features (rownames) present in the input matrix, making uniquePerforming log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|

  |                                                                                                                                
  |                                                                                                                          |   0%
  |                                                                                                                                
  |====                                                                                                                      |   3%
  |                                                                                                                                
  |=======                                                                                                                   |   6%
  |                                                                                                                                
  |===========                                                                                                               |   9%
  |                                                                                                                                
  |==============                                                                                                            |  12%
  |                                                                                                                                
  |==================                                                                                                        |  15%
  |                                                                                                                                
  |======================                                                                                                    |  18%
  |                                                                                                                                
  |=========================                                                                                                 |  21%
  |                                                                                                                                
  |=============================                                                                                             |  24%
  |                                                                                                                                
  |================================                                                                                          |  26%
  |                                                                                                                                
  |====================================                                                                                      |  29%
  |                                                                                                                                
  |=======================================                                                                                   |  32%
  |                                                                                                                                
  |===========================================                                                                               |  35%
  |                                                                                                                                
  |===============================================                                                                           |  38%
  |                                                                                                                                
  |==================================================                                                                        |  41%
  |                                                                                                                                
  |======================================================                                                                    |  44%
  |                                                                                                                                
  |=========================================================                                                                 |  47%
  |                                                                                                                                
  |=============================================================                                                             |  50%
  |                                                                                                                                
  |=================================================================                                                         |  53%
  |                                                                                                                                
  |====================================================================                                                      |  56%
  |                                                                                                                                
  |========================================================================                                                  |  59%
  |                                                                                                                                
  |===========================================================================                                               |  62%
  |                                                                                                                                
  |===============================================================================                                           |  65%
  |                                                                                                                                
  |===================================================================================                                       |  68%
  |                                                                                                                                
  |======================================================================================                                    |  71%
  |                                                                                                                                
  |==========================================================================================                                |  74%
  |                                                                                                                                
  |=============================================================================================                             |  76%
  |                                                                                                                                
  |=================================================================================================                         |  79%
  |                                                                                                                                
  |====================================================================================================                      |  82%
  |                                                                                                                                
  |========================================================================================================                  |  85%
  |                                                                                                                                
  |============================================================================================================              |  88%
  |                                                                                                                                
  |===============================================================================================================           |  91%
  |                                                                                                                                
  |===================================================================================================================       |  94%
  |                                                                                                                                
  |======================================================================================================================    |  97%
  |                                                                                                                                
  |==========================================================================================================================| 100%
atac.seu <- RenameCells(atac.seu, new.names = orig.ATAC@metaData$barcode)
## Add cell type predictions
getPredictedLabels <- function(seu.int, int.name, id.col="predicted.id", score.col="score", filter_score=0){
  pred.df <- seu.int$ATAC@meta.data[,c(id.col, score.col), drop=F] 
  colnames(pred.df) <- c('predicted.id', "score")
  pred.df <- pred.df %>%
    rownames_to_column("cell") %>%
    mutate(predicted.id = ifelse(score < filter_score, NA, as.character(predicted.id))) %>%
    column_to_rownames("cell")
  rownames(pred.df) <- str_remove(rownames(pred.df), "^ATAC_")
  colnames(pred.df) <- c(str_c("predicted.id", "_", int.name), str_c("score", "_", int.name))
  pred.df
  }
pred.cca <- getPredictedLabels(seu.cca, "CCA", score.col = "prediction.score.max")
pred.liger <- getPredictedLabels(seu.liger, "Liger")
pred.conos <- getPredictedLabels(seu.conos, "Conos")
if (all(rownames(pred.conos) == rownames(pred.cca)) & all(rownames(pred.conos) == rownames(pred.liger))) {
  atac.seu <- AddMetaData(atac.seu, metadata = cbind(pred.cca, pred.liger, pred.conos))
} else {
  stop("Non corresponding cell names")
}

Filter low confidence calls

ggpubr::ggarrange(
  plotlist = list(
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_CCA", cols=cell.type.pal) + 
      scale_color_manual(values = cell.type.pal, na.value="grey80") +
      ggtitle("CCA"),
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Liger", cols=cell.type.pal) + 
      scale_color_manual(values = cell.type.pal, na.value="grey80") + ggtitle("Liger"),
    DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Conos", cols=cell.type.pal) + 
      scale_color_manual(values = cell.type.pal, na.value="grey80") + ggtitle("Conos"),
    DimPlot(orig.RNA.seu, reduction = "umap", group.by = "annotation", cols=cell.type.pal) + ggtitle("RNA") +
      scale_color_manual(values = cell.type.pal, na.value="grey80")
  ),
  common.legend = TRUE, ncol=4, nrow=1
) +
  ggsave(paste0(outdir, "umap_labels_filtered.png"), width=16, height = 6)
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

pl <-     DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_CCA", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("CCA")
plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues
pl <-     DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Conos", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("Conos")
plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
  If you'd like to see this geom implemented,
  Please open an issue with your example code at
  https://github.com/ropensci/plotly/issues
orig.RNA.seu <- as.Seurat(orig.RNA)
orig.RNA.seu <- FindVariableFeatures(orig.RNA.seu)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
orig.RNA.seu <- ScaleData(orig.RNA.seu)
Centering and scaling data matrix

  |                                                                                                             
  |                                                                                                       |   0%
  |                                                                                                             
  |====================================================                                                   |  50%
  |                                                                                                             
  |=======================================================================================================| 100%
orig.RNA.seu <- RunPCA(orig.RNA.seu)
PC_ 1 
Positive:  TRBC2, TRBC1, HMGA1, HIST1H1C, HIST1H3H, ITM2A, HIST1H2BJ, SMIM24, TRAV13-2, FXYD2 
       TRBV7-2, PCGF5, HIST1H2BH, IL32, HIST1H2BN, CHAC1, RASD1, TRBV27, TRAV13-1, TRAV8-2 
       TRAV8-4, PTPN6, SELL, HIST1H2BG, TAGAP, TRDC, TRAV38-2DV8, TRAV29DV5, TRBV9, TRAV41 
Negative:  CALD1, COL5A2, COL6A1, COL6A2, SPARC, THY1, DCN, COL3A1, NFIB, SPARCL1 
       TSHZ2, CPE, PLAC9, NID1, FKBP10, PTN, FLRT2, MAP1B, EFEMP2, BGN 
       CXCL12, RBP1, LAMB1, AHNAK, COL1A1, COL5A1, FSTL1, LUM, LAMA4, MDK 
PC_ 2 
Positive:  SFRP1, NTRK2, PLAT, ISLR, NRK, SCARA5, ASPN, OSR1, OLFML3, MXRA8 
       CAPN6, PTPRD, PLP1, TMEFF2, CREB3L1, DKK3, CERCAM, MMP2, EBF2, SMOC2 
       CDO1, COL12A1, PDGFRA, LRRC17, THBS2, HTRA3, SFRP2, ANGPTL1, MAB21L1, MXRA5 
Negative:  MKI67, CDK1, NUSAP1, TOP2A, CCNA2, RRM2, UBE2C, BIRC5, KIFC1, TYMS 
       UBE2T, AURKB, CENPF, CENPM, CDCA8, TACC3, NCAPG, TPX2, ASF1B, CDKN3 
       GTSE1, CDCA3, HJURP, SPC25, MAD2L1, CDC20, PLK1, DLGAP5, NUF2, KIF22 
PC_ 3 
Positive:  CCNA2, NUF2, GTSE1, CDCA8, UBE2T, AURKB, PBK, CDK1, NCAPG, NDC80 
       KIFC1, CDCA3, HJURP, MAD2L1, SPC25, PLK1, KIF15, DEPDC1B, BIRC5, CDCA5 
       CKS1B, RRM2, DLGAP5, HMMR, CENPA, KIF22, KIF20A, CDCA2, CENPF, KIF2C 
Negative:  HLA-DRB5, HLA-DRA, TYROBP, HLA-DRB1, HLA-DPA1, HLA-DPB1, HLA-DQA1, C1QC, C1QB, RNASE1 
       HLA-DQB1, A2M, C1QA, CSF1R, STAB1, HCK, HLA-DMB, FOLR2, SAMHD1, LYZ 
       MS4A7, SPI1, CD36, MS4A6A, CYBB, TMEM176B, CD74, IGSF6, MPEG1, MS4A4A 
PC_ 4 
Positive:  GYPA, GYPB, NFE2, GYPE, ANK1, SLC4A1, RHAG, AHSP, DMTN, KLF1 
       GATA1, TMOD1, TMEM56, HBZ, HBG1, GMPR, C17orf99, SMIM5, HBQ1, TSPO2 
       ALAS2, PHOSPHO1, CR1L, TRIM58, HBM, EPB42, RHD, RHCE, SPTA1, SMIM1 
Negative:  TMSB10, TRBC2, CCL21, IL32, CXCL13, TRBC1, APLNR, CCL19, MIF, HMGB1 
       COX4I2, COL4A1, COL15A1, MADCAM1, FDCSP, CCL17, NTS, TNC, CDH5, FABP4 
       CAV1, COL4A2, CRIP2, RGS5, KLRB1, MYLK, IFITM1, IL33, PAPLN, HPN 
PC_ 5 
Positive:  APLNR, CDH5, CAV1, COL15A1, CRIP2, COL4A1, CCL21, KDR, CLDN5, MADCAM1 
       FABP4, IL33, COL4A2, CXCL13, TM4SF1, PODXL, CCL19, COX4I2, ESAM, BCAM 
       NTS, PAPLN, SPNS2, MYLK, C8orf4, ADGRF5, TM4SF18, TGM2, RP11-536O18.1, CXorf36 
Negative:  CSF1R, MS4A4A, CYBB, PLD4, MS4A6A, CD163, HCK, IGSF6, FOLR2, ADAP2 
       CD86, MS4A7, MARCH1, MRC1, F13A1, MPEG1, CD14, SPI1, HLA-DMB, GPR34 
       CD33, TGFBI, CLEC7A, TIMD4, CEBPA, SIGLEC1, CSF2RA, SLC15A3, LY86, AGR2 
orig.RNA.seu <- RunUMAP(orig.RNA.seu, dims=1:40)
10:43:01 UMAP embedding parameters a = 0.9922 b = 1.112
10:43:01 Read 8321 rows and found 40 numeric columns
10:43:01 Using Annoy for neighbor search, n_neighbors = 30
10:43:01 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:43:03 Writing NN index file to temp file /tmp/RtmpsI55IG/file18a72231550b
10:43:03 Searching Annoy index using 1 thread, search_k = 3000
10:43:06 Annoy recall = 100%
10:43:08 Commencing smooth kNN distance calibration using 1 thread
10:43:10 Initializing from normalized Laplacian + noise
10:43:11 Commencing optimization for 500 epochs, with 367644 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:43:32 Optimization finished
plotly::ggplotly(DimPlot(orig.RNA.seu, group.by="annotation"))
ggarrange(plotlist = map(cell.types[!cell.types %in% c( "NK","NA(1)","NA(3)","ILC3","SP (2)")] , ~ compareCluster(.x)), ncol=1) +
  ggsave(paste0(outdir, "umap_clusters.png"), height = 30, width = 10)
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

Prediction score

Quantifies the uncertainty of the prediction. Calculated differently for every method, but used to define which cells are “unassigned”.

orig.composition <- orig.RNA$annotation
orig.frac <- table(orig.composition)/length(orig.composition)
orig.frac.df <- data.frame(orig.frac) %>%
  dplyr::rename(predicted.id=orig.composition, frac.label=Freq) %>%
  mutate(method="original.RNA")
score_cols <- str_subset(colnames(atac.seu@meta.data), 'score_')
label_cols <- str_subset(colnames(atac.seu@meta.data), 'predicted.id_')
pred.labels.df <- imap(list(CCA=pred.cca, Liger=pred.liger, Conos=pred.conos), ~ 
      rownames_to_column(.x, "cell") %>%
      rename_all(funs(str_remove(., str_c("_",.y)))) %>%
      mutate(method=.y)
    ) %>%
  purrr::reduce(bind_rows) %>%
  mutate(score=ifelse(is.na(score), 0, score))
predict_score_hist <- 
  pred.labels.df %>%
  ggplot(aes(score, fill=method)) +
  geom_histogram(position="identity", alpha=0.8, bins=40) +
  facet_grid(method ~.) +
  scale_fill_brewer(palette="Set1") +
  xlab("Label prediction score") +
  theme_bw(base_size = 16) +
  theme(legend.position = "top")
cutoffs <- seq(0,1,0.05)
predict_score_cumedist <-
  pred.labels.df %>%
  group_by(method) %>%
  mutate(bins=cut(score, breaks = cutoffs)) %>%
  mutate(score=as.numeric(str_remove_all(as.character(bins), ".+,|]"))) %>%
  ggplot(aes(score, color=method)) +
  stat_ecdf(size=0.8, alpha=0.7) +
  scale_color_brewer(palette = "Set1") +
  ylab("Fraction of unassigned cells") +
  xlab("Prediction score cutoff") +
  theme_bw(base_size = 16) +
  xlim(0,1) +
  coord_fixed() +
  guides(color="none") 
ggpubr::ggarrange(predict_score_hist, predict_score_cumedist, common.legend = TRUE, widths = c(0.8, 1.2),
          labels=c("A", "B")) +
  ggsave(paste0(outdir, "prediction_score_distribution.png"), height = 6, width = 10)
Removed 47 rows containing non-finite values (stat_ecdf).

ggpubr::ggarrange(
  plotlist = list(
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_CCA"  , coord.fixed = TRUE) + scale_color_viridis_c() + ggtitle("CCA"),
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Liger", coord.fixed = TRUE) + scale_color_viridis_c() + ggtitle("Liger"),
    FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Conos", coord.fixed = TRUE) + scale_color_viridis_c() + ggtitle("Conos")
  ),
  common.legend = TRUE, ncol=3, nrow=1
) +
  ggsave(paste0(outdir, "prediction_score_umaps.png"), height = 7, width=14)
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

Cell type composition

Compare cell type fractions (w uncertainty)

pred.labels.df %>%
  group_by(method) %>%
  drop_na() %>%
  mutate(tot.cells=n()) %>%
  ungroup() %>%
  group_by(method, predicted.id) %>%
  summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=mean(score)) %>%
  mutate(frac.label=tot.label/tot.cells) %>%
  bind_rows(orig.frac.df) %>%
  mutate(orig.rank = orig.rank.df[predicted.id,]) %>%
  mutate(predicted.id=factor(predicted.id, levels=rownames(orig.rank.df)))%>%
  # select(method, predicted.id, frac.label) %>%
  # distinct() %>%
  ggplot(aes(predicted.id, frac.label, fill=mean.score, color=mean.score)) +
  geom_point(size=2) +
  geom_col(width=0.05) +
  coord_flip() +
  # geom_line(aes(group=method)) +
  facet_wrap(method~., nrow=1, ncol=4, scales="free_x") +
  scale_color_viridis_c() +
  scale_fill_viridis_c() +
  ylab("Fraction of cells") +
  theme_bw(base_size = 16) +
  ggsave(paste0(outdir, "cell_type_composition_bars.png"), width = 15, height = 5)
binding character and factor vector, coercing into character vector

Agreement with unsupervised clustering of ATAC data

Calculate which fractions of NNs in bin based graph of ATAC cells have the same annotation

k = 30
atac.seu <- FindNeighbors(atac.seu, assay = "ATAC", reduction = "SnapATAC", dims = 1:20, k.param = k)
Computing nearest neighbor graph
Computing SNN
atac.nn.list <- getNNlist(atac.seu)
knn.score.CCA <- test.knn(atac.nn.list, setNames(pred.cca.filtered$predicted.id_CCA, rownames(pred.cca.filtered)))
p-value will be approximate in the presence of ties
knn.score.conos <- test.knn(atac.nn.list, setNames(pred.conos.filtered$predicted.id_Conos, rownames(pred.conos.filtered)))
p-value will be approximate in the presence of ties
knn.score.liger <- test.knn(atac.nn.list, setNames(pred.liger.filtered$predicted.id_Liger, rownames(pred.liger.filtered)))
p-value will be approximate in the presence of ties
knn_score_df <-
  list(CCA=knn.score.CCA, conos=knn.score.conos, liger=knn.score.liger) %>%
  imap( ~ data.frame(KNN_score = .x$KNN_score, D=.x$D, p.val=.x$p.val, method=.y)) %>%
  # imap( ~ data.frame(KNN_score = .x$KNN_score, cell= names(.x$KNN_score), D=.x$D, p.val=.x$p.val, method=.y)) %>%
  purrr::reduce(bind_rows) %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score)) %>%
  mutate(data="true")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
knn_score_null_df <-
  list(CCA=knn.score.CCA, conos=knn.score.conos, liger=knn.score.liger) %>%
  imap( ~ data.frame(KNN_score = .x$null, D=.x$D, p.val=.x$p.val, method=.y)) %>%
  # imap( ~ data.frame(KNN_score = .x$KNN_score, cell= names(.x$KNN_score), D=.x$D, p.val=.x$p.val, method=.y)) %>%
  purrr::reduce(bind_rows) %>%
  dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score)) %>%
  mutate(data="null")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
bind_rows(knn_score_df, knn_score_null_df) %>%
  ggplot(aes(KNN_score, color=method)) +
  stat_ecdf( aes(alpha=data), size=1) +
  # stat_ecdf(data=. %>% filter(data=="true"), size=1) +
  facet_grid(method~.) +
  scale_alpha_discrete( range=c(0.5,1), name="") +
  scale_color_brewer(palette = "Set1") +
  geom_text(data= . %>% distinct(method, D, p.val), 
            x=1, y=0.05, hjust=1,
            aes(label=glue("KNN score = {round(D, 3)}, p.value: {p.val}"), y=c(0.90, 0.95, 1))) +
  theme_bw(base_size = 16) +
  ylab("ECDF") + xlab("Fraction of KNNs with shared label") +
  ggsave(paste(outdir,"KNN_score_ecdf_unionHVG.png"), height = 6, width=7)
Using alpha for a discrete variable is not advised.

cluster.prob <- (clust.size/length(pred.labels))
pred.labels.prob <- cluster.prob[pred.labels[which(!is.na(pred.labels))]]
(cluster.prob*k)
cluster.prob^k
length(pred.labels)
plot(knn.scores, knn.scores /as.numeric(pred.labels.prob))

hist( knn.scores - as.numeric(pred.labels.prob)*k)

Accessibility of markers

Taking markers from Fig. S2 of JP’s manuscript

thymus.markers <- c("PTPRC", "CD3G", "TYROBP","CD19","HOXA9",'FXYD2',"SH3TC1","CCR9","CD8A", "CD8B","PDCD1", "CRTAM","CD40LG","CCR6","FOXP3","SOX13","ZNF683","KLRD1","TNFSF11","VPREB1","MS4A1", "CLEC9A", "CLEC10A", "LAMP3", "IL3RA", "FCGR3B", "C2","TPSB2",
                    'ITGA2B',"GYPA", "CDH5", "RGS5","CDH1", "PDGFRA","CRABP1")
# pbmc.markers <- c("CD79A", "MS4A1", "CD8A", "CD8B", "LYZ")
# thymus.markers <- list(Fb=c("PDGFRA", "COLEC11", "FBN1", "PI16"),
#                        VSMC=c("PDGFRB", 'ACTA2', "RGS5"),
#                        Endo=c("PECAM1", "CDH5","LYVE1"),
#                        TEC = c("EPCAM", "FOXN1", "CCL25", "CCL19")
#                        )
thymus.markers.df <- imap(thymus.markers, ~ data.frame(gene=.x, cell.type.class=.y)) %>%
  purrr::reduce(bind_rows)
marker.access.df <- atac.seu@assays$ACTIVITY@data[intersect(thymus.markers, rownames(atac.seu@assays$ACTIVITY)),] %>%
  as.matrix() %>%
  reshape2::melt(varnames=c("gene", "cell"), value.name="log.counts") %>%
  full_join(rownames_to_column(atac.seu@meta.data[, label_cols], "cell")) %>%
  # full_join(thymus.markers.df) %>%
  pivot_longer(cols=label_cols, names_to = "method", values_to = "predicted.id") %>%
  dplyr::mutate(method=str_remove(method,".+_")) %>%
  filter(method %in% c("CCA", "Liger", "Conos")) 
ordered_cell_types <- c("DN", "DP (Q)", "DP (P)", "SP (1)", "NK", "ILC3", "DC", "Mac", "Ery", "Fib")
markers_pl <- 
  marker.access.df %>%
  mutate(predicted.id = case_when(str_detect(predicted.id, "CD8") ~ "CD8+T",
                                  # str_detect(predicted.id, "CD4") ~ "CD4+T",
                                  TRUE ~ predicted.id
                                  )
         ) %>%
  mutate(predicted.id=factor(predicted.id, levels = ordered_cell_types)) %>%
  group_by(method, predicted.id, gene) %>%
  dplyr::mutate(frac.cells=sum(log.counts > 0)/n()) %>%
  # filter(method=="CCA") %>%
  ungroup() %>%
  ggplot( aes( gene, predicted.id)) +
  geom_point(aes(size=frac.cells, color=frac.cells)) +
  facet_grid(method~., space="free", scales="free_x") +
  scale_color_gradient(high="darkblue", low="white") +
  # scale_color_viridis_c() +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.x = element_text(angle=45)) 
markers_pl 

  
ggsave(paste0(outdir, "Thymus_markers_accessibility.png"), height = 16, width = 12)

Reproducing Fig.2H on T-cell development

tcells.markers.df %>%
  full_join(t.cell.markers.df) %>%
  # filter(method=="CCA") %>%
  mutate(predicted.id=factor(predicted.id, levels=ordered.tcells)) %>%
  ggplot(aes( predicted.id, gene)) +
  facet_grid(cell.type.class~method, scales = "free_y", space="free") +
  geom_point(aes(size=frac.cells, color=frac.cells)) +
  # scale_color_gradient(high="darkblue", low="white") +
  scale_color_viridis_c() +
  # scale_color_gradient2(midpoint = 0.5) +
  theme_bw(base_size = 16) +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
        strip.text.y = element_text(angle=0)) 
Joining, by = "gene"
Column `gene` joining factor and character vector, coercing into character vector

Thoughts

  • Conos scores a lot of cells with high confidence, but fails to assign cells to difficult clusters
  • CCA resembles the composition of the RNA data better, but curious that the other methods identify way more
LS0tCnRpdGxlOiAiTGFiZWwgdHJhbnNmZXIgRURBIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNuYXBBVEFDKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEZXNjVG9vbHMpICAjIDQgQVVDIGZ1bmN0aW9uCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShnZ2FsbHV2aWFsKSAgIyA0IHJpdmVyIHBsb3QKbGlicmFyeShnZ3B1YnIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3V0aWxzLlIiKQpzb3VyY2UoIn4vbXVsdGlPbWljX2JlbmNobWFyay9LTk5fYWdyZWVtZW50LlIiKQoKCmdnX2NvbG9yX2h1ZSA8LSBmdW5jdGlvbihuKSB7CiAgaHVlcyA9IHNlcSgxNSwgMzc1LCBsZW5ndGggPSBuICsgMSkKICBoY2woaCA9IGh1ZXMsIGwgPSA2NSwgYyA9IDEwMClbMTpuXQp9CgojIyBNYWtlIG91dHB1dCBkaXJlY3RvcnkKb3V0ZGlyIDwtICJ+L211bHRpT21pY19iZW5jaG1hcmsvcmVwb3J0L291dHB1dC8yMDE5MTExM19sYWJlbFRyYW5zZmVyRURBX0Y3NF92Mi8iCmlmZWxzZSghZGlyLmV4aXN0cyhvdXRkaXIpLCBkaXIuY3JlYXRlKG91dGRpciksIEZBTFNFKQpgYGAKCgpgYGB7cn0KIyBtb2RlbC5jY2EgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxDQ0FfdW5pb25faHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLlJEUyIpCiMgbW9kZWwubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxMaWdlcl91bmlvbl9odmdfRjc0X1NDRWxpc3RfMjAxOTExMTMuUkRTIikKIyBtb2RlbC5jb25vcyA8LSByZWFkUkRTKCJ+L21vZGVscy9tb2RlbENvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQoKc2V1LmNjYSA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQpzZXUubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckxpZ2VyX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQpzZXUuY29ub3MgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQoKCmludGVncmF0ZV9mZWF0dXJlcyA8LSBzY2FuKCJ+L2ludEZlYXR1cmVzX3VuaW9uX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLnR4dCIsIHdoYXQ9JycpCgppbnQubGlzdCA8LSBsaXN0KENDQT1zZXUuY2NhLCBMaWdlcj1zZXUubGlnZXIsIENvbm9zPXNldS5jb25vcykKCiMgIyMgTWFrZSBtZXRob2QgY29sb3IgcGFsZXR0ZQojIG1ldGhvZC5wYWxldHRlIDwtIGJyZXdlcl9wYWxldHRlXzRfdmFsdWVzKG5hbWVzKGludC5saXN0KSwgIlNldDEiKQoKYGBgCgojIyMgRW1iZWRkaW5ncwpWaXN1YWxpemUgbGFiZWwgdHJhbnNmZXIgb24gb3JpZ2luYWwgQVRBQyBkYXRhIChlbWJlZGRlZCBTbmFwQVRBQyBiaW5zKQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyMgTG9hZCBvcmlnaW5hbCBkYXRhCm9yaWcuQVRBQyA8LSByZWFkUkRTKCJ+L215X2RhdGEvY2VsbHJhbmdlci1hdGFjMTEwX2NvdW50XzMwNDM5X1dTU1M4MDM4MzYwX0dSQ2gzOC0xXzFfMC5zbmFwQVRBQy5SRFMiKQpzY2UubGlzdCA8LSByZWFkUkRTKCJ+L215X2RhdGEvaW50ZWdyYXRlZF90aHltdXMvRjc0X1NDRWxpc3RfMjAxOTExMTkuUkRTIikKb3JpZy5STkEgPC0gc2NlLmxpc3QkUk5BCgojIyBNYWtlIFNldXJhdE9iamVjdHMKYXRhYy5zZXUgPC0gc25hcFRvU2V1cmF0KAogICAgb2JqPW9yaWcuQVRBQywgCiAgICBlaWdzLmRpbXM9MToxNiwgCiAgICBub3JtPVRSVUUsCiAgICBzY2FsZT1UUlVFCiAgICApCmF0YWMuc2V1IDwtIFJlbmFtZUNlbGxzKGF0YWMuc2V1LCBuZXcubmFtZXMgPSBvcmlnLkFUQUNAbWV0YURhdGEkYmFyY29kZSkKCiMjIEFkZCBjZWxsIHR5cGUgcHJlZGljdGlvbnMKcHJlZC5jY2EgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jY2EsICJDQ0EiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKQpwcmVkLmxpZ2VyIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIsICJMaWdlciIpCnByZWQuY29ub3MgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jb25vcywgIkNvbm9zIikKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYSwgcHJlZC5saWdlciwgcHJlZC5jb25vcykpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTh9CiMjIG1ha2UgY2VsbCB0eXBlIHBhbGV0dGUKY2VsbC50eXBlcyA8LSBsZXZlbHMoc2V1LmNjYSRSTkEkYW5ub3RhdGlvbikKY2VsbC50eXBlLnBhbCA8LSBicmV3ZXJfcGFsZXR0ZV80X3ZhbHVlcyhjZWxsLnR5cGVzLCBwYWxldHRlID0gIlNldDEiKSAlPiUgc2V0TmFtZXMoY2VsbC50eXBlcykKYXRhYy5zZXUgPC0gUnVuVU1BUChhdGFjLnNldSwgcmVkdWN0aW9uID0gIlNuYXBBVEFDIiwgcmVkdWN0aW9uLm5hbWUgPSAidW1hcC5zbmFwIiwgZGltcz0xOjE2KQoKIyMgRW1iZWRkaW5nIFJOQQpvcmlnLlJOQS5zZXUgPC0gYXMuU2V1cmF0KG9yaWcuUk5BKQpvcmlnLlJOQS5zZXUgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMob3JpZy5STkEuc2V1KQpvcmlnLlJOQS5zZXUgPC0gU2NhbGVEYXRhKG9yaWcuUk5BLnNldSkKb3JpZy5STkEuc2V1IDwtIFJ1blBDQShvcmlnLlJOQS5zZXUpCm9yaWcuUk5BLnNldSA8LSBSdW5VTUFQKG9yaWcuUk5BLnNldSwgZGltcz0xOjQwKQoKCmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gbGlzdCgKICAgIERpbVBsb3Qob3JpZy5STkEuc2V1LCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gImFubm90YXRpb24iLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiUk5BIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIgICwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkNDQSIpLAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9MaWdlciIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJMaWdlciIpLAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9Db25vcyIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpCiAgKSwKICBjb21tb24ubGVnZW5kID0gVFJVRSwgbmNvbD00LCBucm93PTEKKSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bWFwX2xhYmVscy5wbmciKSwgd2lkdGg9MTcsIGhlaWdodCA9IDYpCgoKYGBgCgpGaWx0ZXIgbG93IGNvbmZpZGVuY2UgY2FsbHMgCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xOH0KcHJlZC5jY2EuZmlsdGVyZWQgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jY2EsICJDQ0EiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiLCBmaWx0ZXJfc2NvcmUgPSAwLjUpCnByZWQubGlnZXIuZmlsdGVyZWQgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5saWdlciwgIkxpZ2VyIiwgZmlsdGVyX3Njb3JlID0gMC41KQpwcmVkLmNvbm9zLmZpbHRlcmVkIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY29ub3MsICJDb25vcyIsIGZpbHRlcl9zY29yZSA9IDAuNSkKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYS5maWx0ZXJlZCwgcHJlZC5saWdlci5maWx0ZXJlZCwgcHJlZC5jb25vcy5maWx0ZXJlZCkpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KCmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gbGlzdCgKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ0NBIiwgY29scz1jZWxsLnR5cGUucGFsKSArIAogICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2VsbC50eXBlLnBhbCwgbmEudmFsdWU9ImdyZXk4MCIpICsKICAgICAgZ2d0aXRsZSgiQ0NBIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0xpZ2VyIiwgY29scz1jZWxsLnR5cGUucGFsKSArIAogICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2VsbC50eXBlLnBhbCwgbmEudmFsdWU9ImdyZXk4MCIpICsgZ2d0aXRsZSgiTGlnZXIiKSwKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ29ub3MiLCBjb2xzPWNlbGwudHlwZS5wYWwpICsgCiAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjZWxsLnR5cGUucGFsLCBuYS52YWx1ZT0iZ3JleTgwIikgKyBnZ3RpdGxlKCJDb25vcyIpLAogICAgRGltUGxvdChvcmlnLlJOQS5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAiYW5ub3RhdGlvbiIsIGNvbHM9Y2VsbC50eXBlLnBhbCkgKyBnZ3RpdGxlKCJSTkEiKSArCiAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjZWxsLnR5cGUucGFsLCBuYS52YWx1ZT0iZ3JleTgwIikKICApLAogIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBuY29sPTQsIG5yb3c9MQopICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInVtYXBfbGFiZWxzX2ZpbHRlcmVkLnBuZyIpLCB3aWR0aD0xNiwgaGVpZ2h0ID0gNikKCmBgYAoKCmBgYHtyfQpwbCA8LSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ0NBIikKcGxvdGx5OjpnZ3Bsb3RseShwbCkKYGBgCgpgYGB7cn0KcGwgPC0gICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ29ub3MiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQpwbG90bHk6OmdncGxvdGx5KHBsKQpgYGAKCmBgYHtyfQoKcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KG9yaWcuUk5BLnNldSwgZ3JvdXAuYnk9ImFubm90YXRpb24iKSkKYGBgCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MjB9CmNvbXBhcmVDbHVzdGVyIDwtIGZ1bmN0aW9uKGNsdXN0KXsKICBwbG90bGlzdCA8LSBtYXAobGlzdCgiQ0NBIiwgIkxpZ2VyIiwgIkNvbm9zIiksIAogICAgICAgICAgICAgICAgICB+IEZlYXR1cmVQbG90Q2x1c3RlcihhdGFjLnNldSwgYW5ub3RhdGlvbl9jb2wgPSBnbHVlKCdwcmVkaWN0ZWQuaWRfey54fScpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlX2NvbD1nbHVlKCJzY29yZV97Lnh9IiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcj1jbHVzdCwgbGFiZWw9Z2x1ZSgne2NsdXN0fSAtIHsueH0nKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICkKICBybmFfcGxvdCA8LSBEaW1QbG90Q2x1c3RlcihvcmlnLlJOQS5zZXUsIGFubm90YXRpb25fY29sID0gImFubm90YXRpb24iLCBjbHVzdGVyID0gY2x1c3QsIGxhYmVsPWdsdWUoIntjbHVzdH0gLSBSTkEiKSwgcmVkdWN0ID0gInVtYXAiKQogIHBsb3RsaXN0W1s0XV0gPC0gcm5hX3Bsb3QKICBnZ2FycmFuZ2UocGxvdGxpc3QgPSBwbG90bGlzdCwgbnJvdz0xKQogIH0KCmdnYXJyYW5nZShwbG90bGlzdCA9IG1hcChjZWxsLnR5cGVzWyFjZWxsLnR5cGVzICVpbiUgYyggIk5LIiwiTkEoMSkiLCJOQSgzKSIsIklMQzMiLCJTUCAoMikiKV0gLCB+IGNvbXBhcmVDbHVzdGVyKC54KSksIG5jb2w9MSkgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW1hcF9jbHVzdGVycy5wbmciKSwgaGVpZ2h0ID0gMzAsIHdpZHRoID0gMTApCmBgYAoKIyMgUHJlZGljdGlvbiBzY29yZQpRdWFudGlmaWVzIHRoZSB1bmNlcnRhaW50eSBvZiB0aGUgcHJlZGljdGlvbi4gQ2FsY3VsYXRlZCBkaWZmZXJlbnRseSBmb3IgZXZlcnkgbWV0aG9kLCBidXQgdXNlZCB0byBkZWZpbmUgd2hpY2ggY2VsbHMgYXJlICJ1bmFzc2lnbmVkIi4KCgpgYGB7cn0Kb3JpZy5jb21wb3NpdGlvbiA8LSBvcmlnLlJOQSRhbm5vdGF0aW9uCm9yaWcuZnJhYyA8LSB0YWJsZShvcmlnLmNvbXBvc2l0aW9uKS9sZW5ndGgob3JpZy5jb21wb3NpdGlvbikKCm9yaWcuZnJhYy5kZiA8LSBkYXRhLmZyYW1lKG9yaWcuZnJhYykgJT4lCiAgZHBseXI6OnJlbmFtZShwcmVkaWN0ZWQuaWQ9b3JpZy5jb21wb3NpdGlvbiwgZnJhYy5sYWJlbD1GcmVxKSAlPiUKICBtdXRhdGUobWV0aG9kPSJvcmlnaW5hbC5STkEiKQoKc2NvcmVfY29scyA8LSBzdHJfc3Vic2V0KGNvbG5hbWVzKGF0YWMuc2V1QG1ldGEuZGF0YSksICdzY29yZV8nKQpsYWJlbF9jb2xzIDwtIHN0cl9zdWJzZXQoY29sbmFtZXMoYXRhYy5zZXVAbWV0YS5kYXRhKSwgJ3ByZWRpY3RlZC5pZF8nKQoKcHJlZC5sYWJlbHMuZGYgPC0gaW1hcChsaXN0KENDQT1wcmVkLmNjYSwgTGlnZXI9cHJlZC5saWdlciwgQ29ub3M9cHJlZC5jb25vcyksIH4gCiAgICAgIHJvd25hbWVzX3RvX2NvbHVtbigueCwgImNlbGwiKSAlPiUKICAgICAgcmVuYW1lX2FsbChmdW5zKHN0cl9yZW1vdmUoLiwgc3RyX2MoIl8iLC55KSkpKSAlPiUKICAgICAgbXV0YXRlKG1ldGhvZD0ueSkKICAgICkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICU+JQogIG11dGF0ZShzY29yZT1pZmVsc2UoaXMubmEoc2NvcmUpLCAwLCBzY29yZSkpCgpwcmVkaWN0X3Njb3JlX2hpc3QgPC0gCiAgcHJlZC5sYWJlbHMuZGYgJT4lCiAgZ2dwbG90KGFlcyhzY29yZSwgZmlsbD1tZXRob2QpKSArCiAgZ2VvbV9oaXN0b2dyYW0ocG9zaXRpb249ImlkZW50aXR5IiwgYWxwaGE9MC44LCBiaW5zPTQwKSArCiAgZmFjZXRfZ3JpZChtZXRob2Qgfi4pICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKwogIHhsYWIoIkxhYmVsIHByZWRpY3Rpb24gc2NvcmUiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKCmN1dG9mZnMgPC0gc2VxKDAsMSwwLjA1KQpwcmVkaWN0X3Njb3JlX2N1bWVkaXN0IDwtCiAgcHJlZC5sYWJlbHMuZGYgJT4lCiAgZ3JvdXBfYnkobWV0aG9kKSAlPiUKICBtdXRhdGUoYmlucz1jdXQoc2NvcmUsIGJyZWFrcyA9IGN1dG9mZnMpKSAlPiUKICBtdXRhdGUoc2NvcmU9YXMubnVtZXJpYyhzdHJfcmVtb3ZlX2FsbChhcy5jaGFyYWN0ZXIoYmlucyksICIuKyx8XSIpKSkgJT4lCiAgZ2dwbG90KGFlcyhzY29yZSwgY29sb3I9bWV0aG9kKSkgKwogIHN0YXRfZWNkZihzaXplPTAuOCwgYWxwaGE9MC43KSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICB5bGFiKCJGcmFjdGlvbiBvZiB1bmFzc2lnbmVkIGNlbGxzIikgKwogIHhsYWIoIlByZWRpY3Rpb24gc2NvcmUgY3V0b2ZmIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgeGxpbSgwLDEpICsKICBjb29yZF9maXhlZCgpICsKICBndWlkZXMoY29sb3I9Im5vbmUiKSAKCmdncHVicjo6Z2dhcnJhbmdlKHByZWRpY3Rfc2NvcmVfaGlzdCwgcHJlZGljdF9zY29yZV9jdW1lZGlzdCwgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIHdpZHRocyA9IGMoMC44LCAxLjIpLAogICAgICAgICAgbGFiZWxzPWMoIkEiLCAiQiIpKSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJwcmVkaWN0aW9uX3Njb3JlX2Rpc3RyaWJ1dGlvbi5wbmciKSwgaGVpZ2h0ID0gNiwgd2lkdGggPSAxMCkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTh9CmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gbGlzdCgKICAgIEZlYXR1cmVQbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZmVhdHVyZSA9ICJzY29yZV9DQ0EiICAsIGNvb3JkLmZpeGVkID0gVFJVRSkgKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArIGdndGl0bGUoIkNDQSIpLAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0xpZ2VyIiwgY29vcmQuZml4ZWQgPSBUUlVFKSArIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsgZ2d0aXRsZSgiTGlnZXIiKSwKICAgIEZlYXR1cmVQbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZmVhdHVyZSA9ICJzY29yZV9Db25vcyIsIGNvb3JkLmZpeGVkID0gVFJVRSkgKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArIGdndGl0bGUoIkNvbm9zIikKICApLAogIGNvbW1vbi5sZWdlbmQgPSBUUlVFLCBuY29sPTMsIG5yb3c9MQopICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInByZWRpY3Rpb25fc2NvcmVfdW1hcHMucG5nIiksIGhlaWdodCA9IDcsIHdpZHRoPTE0KQpgYGAKYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD01fQpwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCkgJT4lCiAgbXV0YXRlKG1lZGlhbi5zY29yZT1tZWRpYW4oc2NvcmUpLCBzaXplPW4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KG1ldGhvZCkgJT4lCiAgbXV0YXRlKHJhbmsgPSBkZW5zZV9yYW5rKG1lZGlhbi5zY29yZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICAjIGdncGxvdChhZXMoc2l6ZSwgbWVkaWFuLnNjb3JlKSkgKyBnZW9tX3BvaW50KGFlcyhjb2xvcj1tZXRob2QpKSArCiAgZ2dwbG90KGFlcyhhcy5mYWN0b3IocmFuayksIHNjb3JlLCBmaWxsPXByZWRpY3RlZC5pZCkpICsKICAjIGdlb21fdmlvbGluKCkgKwogIGdlb21fYm94cGxvdCh2YXJ3aWR0aCA9IEYpICsKICAjIGdnYmVlc3dhcm06Omdlb21fcXVhc2lyYW5kb20oYWxwaGE9MC4zKSArCiAgIyBnZW9tX3BvaW50KGFlcyh5PW1lZGlhbi5zY29yZSkpICsKICAjIHN0YXRfZWNkZigpICsKICBmYWNldF93cmFwKG1ldGhvZH4uLCBuY29sPTMsIHNjYWxlcz0iZnJlZV94IikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jZWxsLnR5cGUucGFsKQpgYGAKCgoKIyMgQ2VsbCB0eXBlIGNvbXBvc2l0aW9uCgpDb21wYXJlIGNlbGwgdHlwZSBmcmFjdGlvbnMgKHcgdW5jZXJ0YWludHkpCgpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTd9Cm9yaWcucmFuay5kZiA8LSBvcmlnLmZyYWMuZGYgJT4lIAogIG11dGF0ZShvcmlnLnJhbms9ZGVuc2VfcmFuayhmcmFjLmxhYmVsKSkgJT4lCiAgc2VsZWN0KG9yaWcucmFuaywgcHJlZGljdGVkLmlkKSAlPiUKICBkaXN0aW5jdCgpICU+JQogIGFycmFuZ2Uob3JpZy5yYW5rKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoInByZWRpY3RlZC5pZCIpIAoKcHJlZC5sYWJlbHMuZGYgJT4lCiAgZ3JvdXBfYnkobWV0aG9kKSAlPiUKICBkcm9wX25hKCkgJT4lCiAgbXV0YXRlKHRvdC5jZWxscz1uKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShtZXRob2QsIHByZWRpY3RlZC5pZCkgJT4lCiAgc3VtbWFyaXNlKHRvdC5sYWJlbCA9IG4oKSwgdG90LmNlbGxzID0gbWF4KHRvdC5jZWxscyksIG1lYW4uc2NvcmU9bWVhbihzY29yZSkpICU+JQogIG11dGF0ZShmcmFjLmxhYmVsPXRvdC5sYWJlbC90b3QuY2VsbHMpICU+JQogIGJpbmRfcm93cyhvcmlnLmZyYWMuZGYpICU+JQogIG11dGF0ZShvcmlnLnJhbmsgPSBvcmlnLnJhbmsuZGZbcHJlZGljdGVkLmlkLF0pICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQ9ZmFjdG9yKHByZWRpY3RlZC5pZCwgbGV2ZWxzPXJvd25hbWVzKG9yaWcucmFuay5kZikpKSU+JQogICMgc2VsZWN0KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBmcmFjLmxhYmVsKSAlPiUKICAjIGRpc3RpbmN0KCkgJT4lCiAgZ2dwbG90KGFlcyhwcmVkaWN0ZWQuaWQsIGZyYWMubGFiZWwsIGZpbGw9bWVhbi5zY29yZSwgY29sb3I9bWVhbi5zY29yZSkpICsKICBnZW9tX3BvaW50KHNpemU9MikgKwogIGdlb21fY29sKHdpZHRoPTAuMDUpICsKICBjb29yZF9mbGlwKCkgKwogICMgZ2VvbV9saW5lKGFlcyhncm91cD1tZXRob2QpKSArCiAgZmFjZXRfd3JhcChtZXRob2R+LiwgbnJvdz0xLCBuY29sPTQsIHNjYWxlcz0iZnJlZV94IikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsKICB5bGFiKCJGcmFjdGlvbiBvZiBjZWxscyIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAiY2VsbF90eXBlX2NvbXBvc2l0aW9uX2JhcnMucG5nIiksIHdpZHRoID0gMTUsIGhlaWdodCA9IDUpCmBgYAoKCjwhLS0gRG9lcyB0aGUgdW5jZXJ0YWludHkgZGVwZW5kIG9uIHRoZSBzaXplIG9mIHRoZSBjbHVzdGVyPyAtLT4KPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTV9IC0tPgoKPCEtLSBwcmVkLmxhYmVscy5kZiAlPiUgLS0+CjwhLS0gICBncm91cF9ieShtZXRob2QpICU+JSAtLT4KPCEtLSAgIGRyb3BfbmEoKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUodG90LmNlbGxzPW4oKSkgJT4lIC0tPgo8IS0tICAgdW5ncm91cCgpICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpc2UodG90LmxhYmVsID0gbigpLCB0b3QuY2VsbHMgPSBtYXgodG90LmNlbGxzKSwgbWVhbi5zY29yZT1tZWRpYW4oc2NvcmUpLCBzZC5zY29yZT1tYWQoc2NvcmUpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoZnJhYy5sYWJlbD10b3QubGFiZWwvdG90LmNlbGxzKSAlPiUgLS0+CjwhLS0gICAjIGJpbmRfcm93cyhvcmlnLmZyYWMuZGYpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZnJhYy5sYWJlbCwgbWVhbi5zY29yZSwgY29sb3I9bWV0aG9kKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoc2l6ZT0yKSArIC0tPgo8IS0tICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1tZWFuLnNjb3JlLXNkLnNjb3JlLCB5bWF4PW1lYW4uc2NvcmUrc2Quc2NvcmUpLCBhbHBoYT0wLjYpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsgLS0+CjwhLS0gICAjIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIHNwYW49MS4yKSArIC0tPgo8IS0tICAgZmFjZXRfZ3JpZCguIH4gbWV0aG9kKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsgLS0+CjwhLS0gICBzdGF0X2NvcihsYWJlbC54ID0gMC4yLCBsYWJlbC55PTAuMjUsIGNvbG9yPSJibGFjayIsIHNpemU9NSkgIC0tPgoKCjwhLS0gYGBgIC0tPgoKIyMjIEFncmVlbWVudCB3aXRoIHVuc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIG9mIEFUQUMgZGF0YQpDYWxjdWxhdGUgd2hpY2ggZnJhY3Rpb25zIG9mIE5OcyBpbiBiaW4gYmFzZWQgZ3JhcGggb2YgQVRBQyBjZWxscyBoYXZlIHRoZSBzYW1lIGFubm90YXRpb24KYGBge3J9CmsgPSAzMAphdGFjLnNldSA8LSBGaW5kTmVpZ2hib3JzKGF0YWMuc2V1LCBhc3NheSA9ICJBVEFDIiwgcmVkdWN0aW9uID0gIlNuYXBBVEFDIiwgZGltcyA9IDE6MTYsIGsucGFyYW0gPSBrKQoKYXRhYy5ubi5saXN0IDwtIGdldE5ObGlzdChhdGFjLnNldSkKCmtubi5zY29yZS5DQ0EgPC0gdGVzdC5rbm4oYXRhYy5ubi5saXN0LCBzZXROYW1lcyhwcmVkLmNjYS5maWx0ZXJlZCRwcmVkaWN0ZWQuaWRfQ0NBLCByb3duYW1lcyhwcmVkLmNjYS5maWx0ZXJlZCkpKQprbm4uc2NvcmUuY29ub3MgPC0gdGVzdC5rbm4oYXRhYy5ubi5saXN0LCBzZXROYW1lcyhwcmVkLmNvbm9zLmZpbHRlcmVkJHByZWRpY3RlZC5pZF9Db25vcywgcm93bmFtZXMocHJlZC5jb25vcy5maWx0ZXJlZCkpKQprbm4uc2NvcmUubGlnZXIgPC0gdGVzdC5rbm4oYXRhYy5ubi5saXN0LCBzZXROYW1lcyhwcmVkLmxpZ2VyLmZpbHRlcmVkJHByZWRpY3RlZC5pZF9MaWdlciwgcm93bmFtZXMocHJlZC5saWdlci5maWx0ZXJlZCkpKQoKa25uX3Njb3JlX2RmIDwtCiAgbGlzdChDQ0E9a25uLnNjb3JlLkNDQSwgY29ub3M9a25uLnNjb3JlLmNvbm9zLCBsaWdlcj1rbm4uc2NvcmUubGlnZXIpICU+JQogIGltYXAoIH4gZGF0YS5mcmFtZShLTk5fc2NvcmUgPSAueCRLTk5fc2NvcmUsIEQ9LngkRCwgcC52YWw9LngkcC52YWwsIG1ldGhvZD0ueSkpICU+JQogICMgaW1hcCggfiBkYXRhLmZyYW1lKEtOTl9zY29yZSA9IC54JEtOTl9zY29yZSwgY2VsbD0gbmFtZXMoLngkS05OX3Njb3JlKSwgRD0ueCRELCBwLnZhbD0ueCRwLnZhbCwgbWV0aG9kPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICU+JQogIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpKSAlPiUKICBtdXRhdGUoZGF0YT0idHJ1ZSIpCmtubl9zY29yZV9udWxsX2RmIDwtCiAgbGlzdChDQ0E9a25uLnNjb3JlLkNDQSwgY29ub3M9a25uLnNjb3JlLmNvbm9zLCBsaWdlcj1rbm4uc2NvcmUubGlnZXIpICU+JQogIGltYXAoIH4gZGF0YS5mcmFtZShLTk5fc2NvcmUgPSAueCRudWxsLCBEPS54JEQsIHAudmFsPS54JHAudmFsLCBtZXRob2Q9LnkpKSAlPiUKICAjIGltYXAoIH4gZGF0YS5mcmFtZShLTk5fc2NvcmUgPSAueCRLTk5fc2NvcmUsIGNlbGw9IG5hbWVzKC54JEtOTl9zY29yZSksIEQ9LngkRCwgcC52YWw9LngkcC52YWwsIG1ldGhvZD0ueSkpICU+JQogIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKSAlPiUKICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSkgJT4lCiAgbXV0YXRlKGRhdGE9Im51bGwiKQoKCmJpbmRfcm93cyhrbm5fc2NvcmVfZGYsIGtubl9zY29yZV9udWxsX2RmKSAlPiUKICBnZ3Bsb3QoYWVzKEtOTl9zY29yZSwgY29sb3I9bWV0aG9kKSkgKwogIHN0YXRfZWNkZiggYWVzKGFscGhhPWRhdGEpLCBzaXplPTEpICsKICAjIHN0YXRfZWNkZihkYXRhPS4gJT4lIGZpbHRlcihkYXRhPT0idHJ1ZSIpLCBzaXplPTEpICsKICBmYWNldF9ncmlkKG1ldGhvZH4uKSArCiAgc2NhbGVfYWxwaGFfZGlzY3JldGUoIHJhbmdlPWMoMC41LDEpLCBuYW1lPSIiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBnZW9tX3RleHQoZGF0YT0gLiAlPiUgZGlzdGluY3QobWV0aG9kLCBELCBwLnZhbCksIAogICAgICAgICAgICB4PTEsIHk9MC4wNSwgaGp1c3Q9MSwKICAgICAgICAgICAgYWVzKGxhYmVsPWdsdWUoIktOTiBzY29yZSA9IHtyb3VuZChELCAzKX0sIHAudmFsdWU6IHtwLnZhbH0iKSwgeT1jKDAuOTAsIDAuOTUsIDEpKSkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgeWxhYigiRUNERiIpICsgeGxhYigiRnJhY3Rpb24gb2YgS05OcyB3aXRoIHNoYXJlZCBsYWJlbCIpICsKICBnZ3NhdmUocGFzdGUob3V0ZGlyLCJLTk5fc2NvcmVfZWNkZl91bmlvbkhWRy5wbmciKSwgaGVpZ2h0ID0gNiwgd2lkdGg9NykKYGBgCmBgYHtyfQpjbHVzdGVyLnByb2IgPC0gKGNsdXN0LnNpemUvbGVuZ3RoKHByZWQubGFiZWxzKSkKcHJlZC5sYWJlbHMucHJvYiA8LSBjbHVzdGVyLnByb2JbcHJlZC5sYWJlbHNbd2hpY2goIWlzLm5hKHByZWQubGFiZWxzKSldXQooY2x1c3Rlci5wcm9iKmspCmNsdXN0ZXIucHJvYl5rCmxlbmd0aChwcmVkLmxhYmVscykKcGxvdChrbm4uc2NvcmVzLCBrbm4uc2NvcmVzIC9hcy5udW1lcmljKHByZWQubGFiZWxzLnByb2IpKQoKaGlzdCgga25uLnNjb3JlcyAtIGFzLm51bWVyaWMocHJlZC5sYWJlbHMucHJvYikqaykKYGBgCgoKPCEtLSAjIyMjIFdoaWNoIGNlbGxzIGFyZSBpbmNvbnNpc3RlbnRseSBhbGlnbmVkPyAtLT4KPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTEwfSAtLT4KPCEtLSBwcmVkLmxhYmVscy5kZiAlPiUgLS0+CjwhLS0gICBzZWxlY3QobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGNlbGwpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKGlzLm5hKHByZWRpY3RlZC5pZCksICJub25lIiwgcHJlZGljdGVkLmlkKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyh4PW1ldGhvZCwgc3RyYXR1bT1wcmVkaWN0ZWQuaWQsIGFsbHV2aXVtPWNlbGwsIGZpbGw9cHJlZGljdGVkLmlkLCBsYWJlbD1wcmVkaWN0ZWQuaWQpKSArIC0tPgo8IS0tICAgZ2VvbV9mbG93KCkgKyAtLT4KPCEtLSAgIGdlb21fc3RyYXR1bShjb2xvcj1OQSkgKyAtLT4KPCEtLSAgIGdlb21fdGV4dChzdGF0PSJzdHJhdHVtIikgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gIyMjIyBXaGljaCBjZWxscyBhcmUgaW5jb25zaXN0ZW50bHkgc2NvcmVkPyAtLT4KPCEtLSBgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTh9IC0tPgo8IS0tIGxpYnJhcnkoZ2dhbGx1dmlhbCkgLS0+CjwhLS0gcHJlZC5sYWJlbHMuZGYgJT4lIC0tPgo8IS0tICAgc2VsZWN0KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBjZWxsKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUocHJlZGljdGVkLmlkPWlmZWxzZShpcy5uYShwcmVkaWN0ZWQuaWQpLCAibm9uZSIsIHByZWRpY3RlZC5pZCkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoeD1tZXRob2QsIHN0cmF0dW09cHJlZGljdGVkLmlkLCBhbGx1dml1bT1jZWxsLCBmaWxsPXByZWRpY3RlZC5pZCwgbGFiZWw9cHJlZGljdGVkLmlkKSkgKyAtLT4KPCEtLSAgIGdlb21fZmxvdygpICsgLS0+CjwhLS0gICBnZW9tX3N0cmF0dW0oKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KHN0YXQ9InN0cmF0dW0iKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpIC0tPgo8IS0tIGBgYCAtLT4KCiMjIEFjY2Vzc2liaWxpdHkgb2YgbWFya2VycwpUYWtpbmcgbWFya2VycyBmcm9tIEZpZy4gUzIgb2YgSlAncyBtYW51c2NyaXB0CmBgYHtyLCBmaWcuaGVpZ2h0PTEzLCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnRoeW11cy5tYXJrZXJzIDwtIGMoIlBUUFJDIiwgIkNEM0ciLCAiVFlST0JQIiwiQ0QxOSIsIkhPWEE5IiwnRlhZRDInLCJTSDNUQzEiLCJDQ1I5IiwiQ0Q4QSIsICJDRDhCIiwiUERDRDEiLCAiQ1JUQU0iLCJDRDQwTEciLCJDQ1I2IiwiRk9YUDMiLCJTT1gxMyIsIlpORjY4MyIsIktMUkQxIiwiVE5GU0YxMSIsIlZQUkVCMSIsIk1TNEExIiwgIkNMRUM5QSIsICJDTEVDMTBBIiwgIkxBTVAzIiwgIklMM1JBIiwgIkZDR1IzQiIsICJDMiIsIlRQU0IyIiwKICAgICAgICAgICAgICAgICAgICAnSVRHQTJCJywiR1lQQSIsICJDREg1IiwgIlJHUzUiLCJDREgxIiwgIlBER0ZSQSIsIkNSQUJQMSIpCiMgcGJtYy5tYXJrZXJzIDwtIGMoIkNENzlBIiwgIk1TNEExIiwgIkNEOEEiLCAiQ0Q4QiIsICJMWVoiKQojIHRoeW11cy5tYXJrZXJzIDwtIGxpc3QoRmI9YygiUERHRlJBIiwgIkNPTEVDMTEiLCAiRkJOMSIsICJQSTE2IiksCiMgICAgICAgICAgICAgICAgICAgICAgICBWU01DPWMoIlBER0ZSQiIsICdBQ1RBMicsICJSR1M1IiksCiMgICAgICAgICAgICAgICAgICAgICAgICBFbmRvPWMoIlBFQ0FNMSIsICJDREg1IiwiTFlWRTEiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIFRFQyA9IGMoIkVQQ0FNIiwgIkZPWE4xIiwgIkNDTDI1IiwgIkNDTDE5IikKIyAgICAgICAgICAgICAgICAgICAgICAgICkKdGh5bXVzLm1hcmtlcnMuZGYgPC0gaW1hcCh0aHltdXMubWFya2VycywgfiBkYXRhLmZyYW1lKGdlbmU9LngsIGNlbGwudHlwZS5jbGFzcz0ueSkpICU+JQogIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKQoKbWFya2VyLmFjY2Vzcy5kZiA8LSBhdGFjLnNldUBhc3NheXMkQUNUSVZJVFlAZGF0YVtpbnRlcnNlY3QodGh5bXVzLm1hcmtlcnMsIHJvd25hbWVzKGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWSkpLF0gJT4lCiAgYXMubWF0cml4KCkgJT4lCiAgcmVzaGFwZTI6Om1lbHQodmFybmFtZXM9YygiZ2VuZSIsICJjZWxsIiksIHZhbHVlLm5hbWU9ImxvZy5jb3VudHMiKSAlPiUKICBmdWxsX2pvaW4ocm93bmFtZXNfdG9fY29sdW1uKGF0YWMuc2V1QG1ldGEuZGF0YVssIGxhYmVsX2NvbHNdLCAiY2VsbCIpKSAlPiUKICAjIGZ1bGxfam9pbih0aHltdXMubWFya2Vycy5kZikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9bGFiZWxfY29scywgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gInByZWRpY3RlZC5pZCIpICU+JQogIGRwbHlyOjptdXRhdGUobWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCIuK18iKSkgJT4lCiAgZmlsdGVyKG1ldGhvZCAlaW4lIGMoIkNDQSIsICJMaWdlciIsICJDb25vcyIpKSAKCm9yZGVyZWRfY2VsbF90eXBlcyA8LSBjKCJETiIsICJEUCAoUSkiLCAiRFAgKFApIiwgIlNQICgxKSIsICJOSyIsICJJTEMzIiwgIkRDIiwgIk1hYyIsICJFcnkiLCAiRmliIikKCm1hcmtlcnNfcGwgPC0gCiAgbWFya2VyLmFjY2Vzcy5kZiAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkID0gY2FzZV93aGVuKHN0cl9kZXRlY3QocHJlZGljdGVkLmlkLCAiQ0Q4IikgfiAiQ0Q4K1QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBzdHJfZGV0ZWN0KHByZWRpY3RlZC5pZCwgIkNENCIpIH4gIkNENCtUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiBwcmVkaWN0ZWQuaWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWZhY3RvcihwcmVkaWN0ZWQuaWQsIGxldmVscyA9IG9yZGVyZWRfY2VsbF90eXBlcykpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBnZW5lKSAlPiUKICBkcGx5cjo6bXV0YXRlKGZyYWMuY2VsbHM9c3VtKGxvZy5jb3VudHMgPiAwKS9uKCkpICU+JQogICMgZmlsdGVyKG1ldGhvZD09IkNDQSIpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBnZ3Bsb3QoIGFlcyggZ2VuZSwgcHJlZGljdGVkLmlkKSkgKwogIGdlb21fcG9pbnQoYWVzKHNpemU9ZnJhYy5jZWxscywgY29sb3I9ZnJhYy5jZWxscykpICsKICBmYWNldF9ncmlkKG1ldGhvZH4uLCBzcGFjZT0iZnJlZSIsIHNjYWxlcz0iZnJlZV94IikgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGhpZ2g9ImRhcmtibHVlIiwgbG93PSJ3aGl0ZSIpICsKICAjIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xLCB2anVzdD0wLjUpLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSkpIAoKbWFya2Vyc19wbCAKICAKZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJUaHltdXNfbWFya2Vyc19hY2Nlc3NpYmlsaXR5LnBuZyIpLCBoZWlnaHQgPSAxNiwgd2lkdGggPSAxMikKYGBgCgpSZXByb2R1Y2luZyBGaWcuMkggb24gVC1jZWxsIGRldmVsb3BtZW50CmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTJ9CnQuY2VsbC5tYXJrZXJzIDwtIGxpc3Qoa25vd24ubWFya2VycyA9IGMoIkNEMzQiLCAiSUdMTDEiLCAiVFJHQzIiLCAiVFJEQyIsICJQVENSQSIsICJUUkJDMiIsICJUUkFDIiwgIkNENCIsICJDRDhBIiwgIkNEOEIiKSwKICAgICAgICAgICAgICAgICAgICAgICBjaGVtb2tpbmUucmVjZXB0b3JzID0gYygiQ0NSOSIsICJDQ1I3IiksCiAgICAgICAgICAgICAgICAgICAgICAgdGNyLmFjdGl2YXRpb24gPSBjKCJDRDUiLCAiQ0QyNyIpLAogICAgICAgICAgICAgICAgICAgICAgIHByb2xpZmVyYXRpb249YygiUENOQSIsICJDREsxIiwgIk1LSTY3IiksCiAgICAgICAgICAgICAgICAgICAgICAgY3ljbGluLkQgPSBjKCJDQ05EMiIsICJDQ05EMyIpLAogICAgICAgICAgICAgICAgICAgICAgIHJlY29tYmluYXRpb249YygiUkFHMSIsICJSQUcyIiksCiAgICAgICAgICAgICAgICAgICAgICAgYXBvcHRvc2lzPWMoIkhSSyIsIkJNRiIsICJUUDUzSU5QMSIpLAogICAgICAgICAgICAgICAgICAgICAgIHN0YWdlLm1hcmtlcnMgPSBjKCJTVDE4IiwgIkhJVkVQMyIsICJSR1BEMyIsICJTTVBEMyIsICJBUVAzIiwgIlJPUkMiLCAiU0FUQjEiLCAiVE9YMiIpCiAgICAgICAgICAgICAgICAgICAgICAgKSAKdC5jZWxsLm1hcmtlcnMuZGYgPC0gaW1hcCh0LmNlbGwubWFya2VycywgfiBkYXRhLmZyYW1lKGdlbmU9LngsIGNlbGwudHlwZS5jbGFzcz0ueSkpICU+JQogIHB1cnJyOjpyZWR1Y2UoYmluZF9yb3dzKQoKb3JkZXJlZC50Y2VsbHMgPC0gYygiRE4iLCAiRFAgKFApIiwgIkRQIChRKSIsIlNQICgxKSIpCgp0Y2VsbHMubWFya2Vycy5kZiA8LSAKICBhdGFjLnNldUBhc3NheXMkQUNUSVZJVFlAZGF0YVtpbnRlcnNlY3QodW5saXN0KHQuY2VsbC5tYXJrZXJzKSwgcm93bmFtZXMoYXRhYy5zZXVAYXNzYXlzJEFDVElWSVRZKSksXSAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSwgdmFsdWUubmFtZT0ibG9nLmNvdW50cyIpICU+JQogIGZ1bGxfam9pbihyb3duYW1lc190b19jb2x1bW4oYXRhYy5zZXVAbWV0YS5kYXRhWywgbGFiZWxfY29sc10sICJjZWxsIikpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPWxhYmVsX2NvbHMsIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJwcmVkaWN0ZWQuaWQiKSAlPiUKICBkcGx5cjo6bXV0YXRlKG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwiLitfIikpICU+JQogIGZpbHRlcihtZXRob2QgJWluJSBjKCJDQ0EiLCAiTGlnZXIiLCAiQ29ub3MiKSkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2Uoc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDgrIiksICJDRDgrVCIsIHByZWRpY3RlZC5pZCkpICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKHN0cl9kZXRlY3QocHJlZGljdGVkLmlkLCAiQ0Q0KyIpLCAiQ0Q0K1QiLCBwcmVkaWN0ZWQuaWQpKSAlPiUKICBmaWx0ZXIocHJlZGljdGVkLmlkICVpbiUgb3JkZXJlZC50Y2VsbHMpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkLCBnZW5lKSAlPiUKICBkcGx5cjo6bXV0YXRlKGZyYWMuY2VsbHM9c3VtKGxvZy5jb3VudHMgPiAwKS9uKCksIG1lYW4uYWNjPW1lYW4obG9nLmNvdW50cykpICU+JQogIHVuZ3JvdXAoKSAKCnRjZWxscy5tYXJrZXJzLmRmICU+JQogIGZ1bGxfam9pbih0LmNlbGwubWFya2Vycy5kZikgJT4lCiAgIyBmaWx0ZXIobWV0aG9kPT0iQ0NBIikgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1mYWN0b3IocHJlZGljdGVkLmlkLCBsZXZlbHM9b3JkZXJlZC50Y2VsbHMpKSAlPiUKICBnZ3Bsb3QoYWVzKCBwcmVkaWN0ZWQuaWQsIGdlbmUpKSArCiAgZmFjZXRfZ3JpZChjZWxsLnR5cGUuY2xhc3N+bWV0aG9kLCBzY2FsZXMgPSAiZnJlZV95Iiwgc3BhY2U9ImZyZWUiKSArCiAgZ2VvbV9wb2ludChhZXMoc2l6ZT1mcmFjLmNlbGxzLCBjb2xvcj1mcmFjLmNlbGxzKSkgKwogICMgc2NhbGVfY29sb3JfZ3JhZGllbnQoaGlnaD0iZGFya2JsdWUiLCBsb3c9IndoaXRlIikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsKICAjIHNjYWxlX2NvbG9yX2dyYWRpZW50MihtaWRwb2ludCA9IDAuNSkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEsIHZqdXN0PTAuNSksCiAgICAgICAgc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTApKSAKCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidGNlbGxfbWFya2Vycy5wbmciKSwgaGVpZ2h0ID0gMTQsIHdpZHRoID0gMTQpCgpgYGAKCjwhLS0gIyMjIENvbXBhcmUgZmVhdHVyZSBzZWxlY3Rpb24gc3RyYXRlZ3kgKHJlZmVyZW5jZSBiYXNlZCkgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHNldS5jY2EucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDQ0FfcmVmZXJlbmNlX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKSAtLT4KPCEtLSBzZXUubGlnZXIucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJMaWdlcl9yZWZlcmVuY2VfaHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLlJEUyIpIC0tPgo8IS0tIHNldS5jb25vcy5yZWYgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3JlZmVyZW5jZV9odmdfRjc0X1NDRWxpc3RfMjAxOTExMDEuUkRTIikgLS0+Cgo8IS0tIGludGVncmF0ZV9mZWF0dXJlc19yZWYgPC0gc2Nhbigifi9tb2RlbHMvaW50RmVhdHVyZXNfcmVmZXJlbmNlX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLnR4dCIsIHdoYXQgPSAiIikgLS0+Cgo8IS0tIGludC5saXN0LnJlZiA8LSBsaXN0KENDQT1zZXUuY2NhLnJlZiwgTGlnZXI9c2V1LmxpZ2VyLnJlZiwgQ29ub3M9c2V1LmNvbm9zLnJlZikgLS0+Cgo8IS0tICMjIEFkZCB0byBhdGFjIFNldXJhdCBvYmplY3QgLS0+CjwhLS0gcHJlZC5jY2EucmVmIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLnJlZiwgIkNDQV9yZWYiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKSAtLT4KPCEtLSBwcmVkLmxpZ2VyLnJlZiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmxpZ2VyLnJlZiwgIkxpZ2VyX3JlZiIpIC0tPgo8IS0tIHByZWQuY29ub3MucmVmIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY29ub3MucmVmLCAiQ29ub3NfcmVmIikgLS0+Cgo8IS0tIGlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsgLS0+CjwhLS0gICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYS5yZWYsIHByZWQubGlnZXIucmVmLCBwcmVkLmNvbm9zLnJlZikpIC0tPgo8IS0tIH0gZWxzZSB7IC0tPgo8IS0tICAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xOSwgZmlnLmhlaWdodD05fSAtLT4KPCEtLSBnZ3B1YnI6OmdnYXJyYW5nZSggLS0+CjwhLS0gICBwbG90bGlzdCA9IGxpc3QoIC0tPgo8IS0tICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQV9yZWYiICAsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwgLS0+CjwhLS0gICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfTGlnZXJfcmVmIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksIC0tPgo8IS0tICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zX3JlZiIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpIC0tPgo8IS0tICAgKSwgLS0+CjwhLS0gICBjb21tb24ubGVnZW5kID0gVFJVRSwgbmNvbD0zLCBucm93PTEgLS0+CjwhLS0gKSAgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTE2fSAtLT4KPCEtLSBwcmVkLmxhYmVscy5yZWYuZGYgPC0gaW1hcChsaXN0KENDQT1wcmVkLmNjYS5yZWYsIExpZ2VyPXByZWQubGlnZXIucmVmLCBDb25vcz1wcmVkLmNvbm9zLnJlZiksIH4gIC0tPgo8IS0tICAgICAgIHJvd25hbWVzX3RvX2NvbHVtbigueCwgImNlbGwiKSAlPiUgLS0+CjwhLS0gICAgICAgcmVuYW1lX2FsbChmdW5zKHN0cl9yZW1vdmUoLiwgc3RyX2MoIl8iLC55KSkpKSAlPiUgLS0+CjwhLS0gICAgICAgbXV0YXRlKG1ldGhvZD0ueSkgLS0+CjwhLS0gICAgICkgJT4lIC0tPgo8IS0tICAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShzY29yZT1pZmVsc2UoaXMubmEoc2NvcmVfcmVmKSwgMCwgc2NvcmVfcmVmKSkgLS0+Cgo8IS0tIGZ1bGxfam9pbiggLS0+CjwhLS0gICBwcmVkLmxhYmVscy5kZiwgLS0+CjwhLS0gICBzZWxlY3QocHJlZC5sYWJlbHMucmVmLmRmLCBjZWxsLCBwcmVkaWN0ZWQuaWRfcmVmLCBzY29yZV9yZWYsIG1ldGhvZCksIC0tPgo8IS0tICAgYnk9YygiY2VsbCIsICJtZXRob2QiKSAtLT4KPCEtLSAgICkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShuX3ByZWQ9bigpKSAlPiUgLS0+CjwhLS0gICB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIHByZWRpY3RlZC5pZF9yZWYpICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZShuPW4oKSwgbl9wcmVkPW1heChuX3ByZWQpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoZnJhYz1uL25fcHJlZCkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhwcmVkaWN0ZWQuaWQsIHByZWRpY3RlZC5pZF9yZWYpKSArIC0tPgo8IS0tICAgZ2VvbV90aWxlKGFlcyhmaWxsPWZyYWMpKSArIC0tPgo8IS0tICAgZmFjZXRfd3JhcChtZXRob2R+LiwgbnJvdz0xLCBuY29sPTMpICsgLS0+CjwhLS0gICBjb29yZF9maXhlZCgpICsgLS0+CjwhLS0gICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoPSJyZWQiKSArIC0tPgo8IS0tICAgeWxhYigiRmVhdC4gc2VsZWN0aW9uOiByZWZlcmVuY2UgSFZHIikgKyB4bGFiKCJGZWF0LiBzZWxlY3Rpb246IHVuaW9uIEhWRyIpICsgLS0+CjwhLS0gICB0aGVtZV9jb3dwbG90KGZvbnRfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKSArIC0tPgo8IS0tICAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bmlvblZTcmVmZXJlbmNlLnBuZyIpLCBoZWlnaHQgPSAxMiwgd2lkdGg9MTApIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+Cgo8IS0tIHNjb3JlLkNDQS5yZWYgPC0gICBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY2NhLnJlZlsueCwxXSA9PSBwcmVkLmNjYS5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5Db25vcy5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNvbm9zLnJlZlsueCwxXSA9PSBwcmVkLmNvbm9zLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkxpZ2VyLnJlZiA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQubGlnZXIucmVmWy54LDFdID09IHByZWQubGlnZXIucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+Cgo8IS0tIGtubl9zY29yZV9yZWZfZGYgPC0gLS0+CjwhLS0gICBhcy5kYXRhLmZyYW1lKGNiaW5kKHNjb3JlLkNvbm9zLnJlZiwgc2NvcmUuTGlnZXIucmVmLCBzY29yZS5DQ0EucmVmKSkgJT4lIC0tPgo8IS0tICAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lIC0tPgo8IS0tICAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RyX3N1YnNldChjb2xuYW1lcyguKSwgInNjb3JlIiksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJLTk5fc2NvcmUiKSAlPiUgLS0+CjwhLS0gICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgIG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgInNjb3JlLiIpKSAtLT4KCjwhLS0gcXVhbnRzID0gc2VxKDAsMSwgYnkgPSAwLjA1KSAtLT4KPCEtLSBBVUVDREZfa25uX3Njb3JlIDwtIGtubl9zY29yZV9yZWZfZGYgJT4lIC0tPgo8IS0tICAgc3BsaXQoLiRtZXRob2QpICU+JSAtLT4KPCEtLSAgIG1hcF9kYmwoIH4gLnggJT4lIC0tPgo8IS0tICAgICAgIGFycmFuZ2UoS05OX3Njb3JlKSAlPiUgIC0tPgo8IS0tICAgICAgIHtlY2RmKC4kS05OX3Njb3JlKShxdWFudHMpfSAlPiUgQVVDKHF1YW50cywuKSAtLT4KPCEtLSAgICAgKSAtLT4KCjwhLS0ga25uX3Njb3JlX3JlZl9kZiAlPiUgLS0+CjwhLS0gICBtdXRhdGUoQVVDPUFVRUNERl9rbm5fc2NvcmVbbWV0aG9kXSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPW1ldGhvZCwgZmlsbD1tZXRob2QpKSArIC0tPgo8IS0tICAgc3RhdF9lY2RmKHNpemU9MSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KGRhdGE9LiAlPiUgZ3JvdXBfYnkobWV0aG9kKSAlPiUgc3VtbWFyaXNlKEFVQz1tYXgoQVVDKSksICAtLT4KPCEtLSAgICAgICAgICAgICB4PTAuMDUsIGhqdXN0PTAsIC0tPgo8IS0tICAgICAgICAgICAgIGFlcyhsYWJlbD1nbHVlKCJBVUMgPSB7cm91bmQoQVVDLCAzKX0iKSwgeT1jKDAuOTAsIDAuOTUsIDEpKSkgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgeWxhYigiRUNERiIpICAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIyBJcyB0aGUgdW5pb24gb3IgdGhlIHJlZmVyZW5jZSBiZXN0IG1haW50YWluaW5nIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIEFUQUM/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD03fSAtLT4KPCEtLSBrID0gNTAgLS0+CjwhLS0gYXRhYy5zZXUgPC0gRmluZE5laWdoYm9ycyhhdGFjLnNldSwgYXNzYXkgPSAiQVRBQyIsIHJlZHVjdGlvbiA9ICJTbmFwQVRBQyIsIGRpbXMgPSAxOjE1LCBrLnBhcmFtID0gaykgLS0+Cgo8IS0tIGF0YWMubm4ubGlzdCA8LSBnZXROTmxpc3QoYXRhYy5zZXUpIC0tPgoKPCEtLSBzY29yZS5DQ0EgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYVsueCwxXSA9PSBwcmVkLmNjYVsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkNvbm9zIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vc1sueCwxXSA9PSBwcmVkLmNvbm9zWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuTGlnZXIgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyWy54LDFdID09IHByZWQubGlnZXJbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KCjwhLS0ga25uX3Njb3JlX2RmIDwtIC0tPgo8IS0tICAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcywgc2NvcmUuTGlnZXIsIHNjb3JlLkNDQSkpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JSAtLT4KPCEtLSAgIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lIC0tPgo8IS0tICAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksIC0tPgo8IS0tICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkgLS0+CgoKPCEtLSBzY29yZS5DQ0EucmVmIDwtICAgaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYS5yZWZbLngsMV0gPT0gcHJlZC5jY2EucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuQ29ub3MucmVmIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vcy5yZWZbLngsMV0gPT0gcHJlZC5jb25vcy5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5MaWdlci5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyLnJlZlsueCwxXSA9PSBwcmVkLmxpZ2VyLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgoKPCEtLSBrbm5fc2NvcmVfcmVmX2RmIDwtIC0tPgo8IS0tICAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcy5yZWYsIHNjb3JlLkxpZ2VyLnJlZiwgc2NvcmUuQ0NBLnJlZikpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JSAtLT4KPCEtLSAgIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lIC0tPgo8IS0tICAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksIC0tPgo8IS0tICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkgLS0+CgoKPCEtLSBiaW5kX3Jvd3Moa25uX3Njb3JlX2RmLCBrbm5fc2NvcmVfcmVmX2RmKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoZmVhdHVyZS5zZWxlY3Rpb249aWZlbHNlKHN0cl9kZXRlY3QobWV0aG9kLCAicmVmIiksICJyZWYiLCAidW5pb24iKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgIi5yZWYiKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPWZlYXR1cmUuc2VsZWN0aW9uLCBmaWxsPW1ldGhvZCkpICsgLS0+CjwhLS0gICBzdGF0X2VjZGYoc2l6ZT0xKSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgLS0+CjwhLS0gICBmYWNldF93cmFwKG1ldGhvZH4uKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsgLS0+CjwhLS0gICB5bGFiKCJFQ0RGIikgKyAtLT4KPCEtLSAgIGdndGl0bGUocGFzdGUoIksgPSIsIGspKSArIC0tPgo8IS0tICAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bmlvblZTcmVmZXJlbmNlX0tOTi5wbmciKSwgaGVpZ2h0ID0gNCwgd2lkdGggPSAxMCkgLS0+Cgo8IS0tIGBgYCAtLT4KCjwhLS0gLS0tIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KG9yaWcuUk5BLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiKSkgLS0+CjwhLS0gcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIpKSAtLT4KPCEtLSBgYGAgLS0+CgoKIyMjIFRob3VnaHRzCi0gQ29ub3Mgc2NvcmVzIGEgbG90IG9mIGNlbGxzIHdpdGggaGlnaCBjb25maWRlbmNlLCBidXQgZmFpbHMgdG8gYXNzaWduIGNlbGxzIHRvIGRpZmZpY3VsdCBjbHVzdGVycyAKLSBDQ0EgcmVzZW1ibGVzIHRoZSBjb21wb3NpdGlvbiBvZiB0aGUgUk5BIGRhdGEgYmV0dGVyLCBidXQgY3VyaW91cyB0aGF0IHRoZSBvdGhlciBtZXRob2RzIGlkZW50aWZ5IHdheSBtb3JlIAoKCgoKCgoKCgoK